#pragma once

#include<iostream>
#include<string>
#include<filesystem>//c++17 文件操作头文件
#include<fstream>
#include<ctime>
#include<unistd.h>
#include<memory>
#include<sstream>
#include"Mutex.hpp"

//[时间] [日志等级] [进程pid] [对应日志的文件名][行号] - 消息内容,支持可变参数

//指定场景的日志等级
enum class LogLevel
{
    DEBUG,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

//将等级转换为字符串返回
std::string Level2String(LogLevel level)
{
    switch (level)
    {
    case LogLevel::DEBUG:
        return "Debug";
    case LogLevel::INFO:
        return "Info";
    case LogLevel::WARNING:
        return "Warning";
    case LogLevel::ERROR:
        return "Error";
    case LogLevel::FATAL:
        return "Fatal";
    default:
        return "Unknown";
    }
}

//获取时间
std::string GetCurrentTime()
{
    //获取时间戳
    time_t currtime = time(nullptr);

    //利用localtime_r.其返回值是一个结构体， struct tm
    // struct tm {
    //        int         tm_sec;    /* Seconds          [0, 60] */
    //        int         tm_min;    /* Minutes          [0, 59] */
    //        int         tm_hour;   /* Hour             [0, 23] */
    //        int         tm_mday;   /* Day of the month [1, 31] */
    //        int         tm_mon;    /* Month            [0, 11]  (January = 0) */
    //        int         tm_year;   /* Year minus 1900 */
    //        int         tm_wday;   /* Day of the week  [0, 6]   (Sunday = 0) */
    //        int         tm_yday;   /* Day of the year  [0, 365] (Jan/01 = 0) */
    //        int         tm_isdst;  /* Daylight savings flag */

    //        long        tm_gmtoff; /* Seconds East of UTC */
    //        const char *tm_zone;   /* Timezone abbreviation */
    //    };
     struct tm currtm;
     localtime_r(&currtime , &currtm);//currtm 作为输出型参数

     //将currtm 中的数据转换成字符串
     char timebuffer[64];
     //snprintf int snprintf(char str[restrict .size], size_t size, const char *restrict format, ...);
     snprintf(timebuffer , sizeof(timebuffer) , "%4d-%02d-%02d %02d:%02d:%02d",
                currtm.tm_year + 1900,
                currtm.tm_mon + 1,/* Month   [0, 11]  (January = 0) */
                currtm.tm_mday,
                currtm.tm_hour,
                currtm.tm_min,
                currtm.tm_sec);

    return timebuffer;
}

//刷新方法：是将日志信息刷新到显式器中还是刷新到文件中
//写成基类
class LogStrategy
{
public:
    //析构
    virtual ~LogStrategy() = default;
    virtual void Synclog(const std::string &logmessage) = 0;//虚函数重写以构成多态
};

//显示器刷新
class ConsoleLogStrategy : public LogStrategy
{
public:
    ~ConsoleLogStrategy()
    {}

    //虚函数重写
    void Synclog(const std::string &logmessage) override
    {
        {        
            //需要上锁
            LockGuard lock(&_lock);
            std::cout<< logmessage <<std::endl;
        }

    }
private:
    Mutex _lock;
};

//默认路径，默认文件名
const std::string logdafaultdir = "./log";//当前路径下的目录
const static std::string logfilename = "test.log";//文件名

//文件刷新
class FileLogStrategy: public LogStrategy
{
public:
    //构造析构
    FileLogStrategy(const std::string& dir = logdafaultdir,
                    const std::string& filename = logfilename)
                    :_dir_path_name(dir)
                    ,_filename(filename)
                    {
                        //对锁进行初始化
                        LockGuard lock(&_lock);
                        //如果不存在该目录，需要新建该目录；判断存在与否使用：exists ,创建目录：create_directories
                        //其中 create_directories 是c++17 中filesystem 中的库函数
                        if(std::filesystem::exists(_dir_path_name))
                        {
                            return;
                        }
                        //捕获异常
                        try
                        {
                            std::filesystem::create_directories(_dir_path_name);
                        }
                        catch(const std::filesystem::filesystem_error& e)
                        {
                            std::cerr << e.what() << "\n";
                        } 
                    }

    //重写SyncLog虚函数
    void Synclog(const std::string& logmessage) override
    {
        {
            //加锁
            LockGuard loc(&_lock);
            //将路径和文件放在一起
            std::string target = _dir_path_name;
            target+='/';
            target += _filename;
            //ofstream 文件输入
            std::ofstream out(target.c_str() , std::ios::app);//追加
            if(!out.is_open())//检查文件是否打开
            {
                return;//文件没有打开，无法写入数据
            }
            out<< logmessage <<"\n" ;//将 logmessage 中的数据写入文件中，相当于write
            out.close();//关闭文件

        }
    }
    ~FileLogStrategy()
    {}

private:
    //路径+文件名+锁
    std::string _dir_path_name;
    std::string _filename;
    Mutex _lock;
};

//接下来需要定制刷新策略，然后构建完整的日志信息
class Logger
{
public:
    Logger()
    {}
    //选择刷新的位置
    void EnableConsoleLogStrategy()
    {
        _strategy = std::make_unique<ConsoleLogStrategy>();
    }
    void EnableFileLogStrategy()
    {
        _strategy = std::make_unique<FileLogStrategy>();
    }
    //形成一条完整的日志信息
    //使用内部类
    class LogMessage
    {
    public:
        //获取时间的函数已经写好了，需要传参：日志等级、文件名、行号
        LogMessage(LogLevel level , std::string& filename , int line , Logger& logger)
            :_curr_time(GetCurrentTime())
            ,_level(level)
            ,_pid(getpid())
            ,_filename(filename)
            ,_line(line)
            ,_logger(logger)
            {
                std::stringstream ss;//会自动将数据转换成字符,字符串流
                ss << "[" << _curr_time <<"] "
                    << "[" << Level2String(_level) <<"] "
                    << "[" << _pid << "] "
                    << "[" << _filename <<"]"
                    << "[" << _line <<"] "
                    << "-";
                _loginfo = ss.str();//将前半部分的内容放入 _loginfo 中 
            }
        //可变参数内容数据也需要放入 _loginfo 中，需要重载 << ， 写成函数模板
        template<typename T>
        LogMessage& operator<< (const T& info)
        {
            std::stringstream ss;//字符串流
            ss<<info;
            _loginfo += ss.str();
            return *this;
        }
        ~LogMessage()
        {
            //析构的时候将_loginfo 中的数据放入显示器、文件等地方中
            if(_logger._strategy)//策略存在
            {
                _logger._strategy->Synclog(_loginfo);
            }
        }
        
    private:
        //[时间] [日志等级] [进程pid] [对应日志的文件名][行号] - 消息内容,支持可变参数
        std::string _curr_time;
        LogLevel _level;
        pid_t _pid;
        std::string _filename;
        int _line;

        std::string _loginfo;//将合并的完整的信息放在这里面
        Logger &_logger;//刷新策略是什么
    };
    //返回临时对象
    LogMessage operator()(LogLevel level , std::string filename , int line)
    {
        return LogMessage(level , filename , line , *this);
    }

    ~Logger()
    {}
private:
    std::unique_ptr<LogStrategy> _strategy;//智能指针
};

//日志，全局
Logger logger;

#define LOG(level) logger(level , __FILE__ ,__LINE__) // 写成宏
#define EnableConsoleLogStrategy() logger.EnableConsoleLogStrategy()
#define EnableFileLogStrategy() logger.EnableFileLogStrategy()